﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Controls;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using HP.Cachalote.DataModels;
using HP.Cachalote.Services.Interfaces;
using HP.Cachalote.Services.Shared;
using HP.Cachalote.WPF.Model;
using HP.Cachalote.WPF.Shared;

namespace HP.Cachalote.WPF.ViewModel
{
    /// <summary>
    /// This class contains properties that a View can data bind to.
    /// <para>
    /// See http://www.galasoft.ch/mvvm
    /// </para>
    /// </summary>
    public class DueTimePickerViewModel : MyViewModelBase
    {
        public const string SelectDueDateTimeCompleteMessageToken = "SelectDueDateTimeComplete";
        public const string RequestCloseDueTimePickerWindowMessageToken = "RequestCloseDueTimePickerWindowMessage";

        public int TodoItemID { get; set; }

        /// <summary>
        /// Initializes a new instance of the DueTimePickerViewModel class.
        /// </summary>
        public DueTimePickerViewModel(IDataService dataService)
            : base(dataService) { }

        /// <summary>
        /// The <see cref="TodoItemTitle" /> property's name.
        /// </summary>
        public const string TodoItemTitlePropertyName = "TodoItemTitle";

        private string _todoItemTitle = string.Empty;

        /// <summary>
        /// Sets and gets the TodoItemTitle property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public string TodoItemTitle
        {
            get
            {
                return _todoItemTitle;
            }

            set
            {
                if (_todoItemTitle == value)
                {
                    return;
                }

                RaisePropertyChanging(TodoItemTitlePropertyName);
                _todoItemTitle = value;
                RaisePropertyChanged(TodoItemTitlePropertyName);
            }
        }

        /// <summary>
        /// The <see cref="DueDate" /> property's name.
        /// </summary>
        public const string DueDatePropertyName = "DueDate";

        private DateTime? _dueDate;

        /// <summary>
        /// Sets and gets the DueDate property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public DateTime? DueDate
        {
            get
            {
                return _dueDate;
            }

            set
            {
                if (_dueDate == value)
                {
                    return;
                }

                RaisePropertyChanging(DueDatePropertyName);
                _dueDate = value;
                RaisePropertyChanged(DueDatePropertyName);
            }
        }

        /// <summary>
        /// The <see cref="StartTime" /> property's name.
        /// </summary>
        public const string StartTimePropertyName = "StartTime";

        private DateTime? _startTime;

        /// <summary>
        /// Sets and gets the StartTime property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public DateTime? StartTime
        {
            get
            {
                return _startTime;
            }

            set
            {
                if (_startTime == value)
                {
                    return;
                }

                RaisePropertyChanging(StartTimePropertyName);
                _startTime = value;
                RaisePropertyChanged(StartTimePropertyName);
            }
        }

        /// <summary>
        /// The <see cref="EndTime" /> property's name.
        /// </summary>
        public const string EndTimePropertyName = "EndTime";

        private DateTime? _endTime;

        /// <summary>
        /// Sets and gets the EndTime property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public DateTime? EndTime
        {
            get
            {
                return _endTime;
            }

            set
            {
                if (_endTime == value)
                {
                    return;
                }

                RaisePropertyChanging(EndTimePropertyName);
                _endTime = value;
                RaisePropertyChanged(EndTimePropertyName);
            }
        }

        /// <summary>
        /// The <see cref="IsStartTimeSpecified" /> property's name.
        /// </summary>
        public const string IsStartTimeSpecifiedPropertyName = "IsStartTimeSpecified";

        private bool _isStartTimeSpecified = true;

        /// <summary>
        /// Sets and gets the IsStartTimeSpecified property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public bool IsStartTimeSpecified
        {
            get
            {
                return _isStartTimeSpecified;
            }

            set
            {
                if (_isStartTimeSpecified == value)
                {
                    return;
                }

                RaisePropertyChanging(IsStartTimeSpecifiedPropertyName);
                _isStartTimeSpecified = value;
                RaisePropertyChanged(IsStartTimeSpecifiedPropertyName);
            }
        }

        /// <summary>
        /// The <see cref="IsEndTimeSpecified" /> property's name.
        /// </summary>
        public const string IsEndTimeSpecifiedPropertyName = "IsEndTimeSpecified";

        private bool _isEndTimeSpecified = true;

        /// <summary>
        /// Sets and gets the IsEndTimeSpecified property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public bool IsEndTimeSpecified
        {
            get
            {
                return _isEndTimeSpecified;
            }

            set
            {
                if (_isEndTimeSpecified == value)
                {
                    return;
                }

                RaisePropertyChanging(IsEndTimeSpecifiedPropertyName);
                _isEndTimeSpecified = value;
                RaisePropertyChanged(IsEndTimeSpecifiedPropertyName);
            }
        }

        /// <summary>
        /// The <see cref="IsAllDayEvent" /> property's name.
        /// </summary>
        public const string IsAllDayEventPropertyName = "IsAllDayEvent";

        private bool _isAllDayEvent = false;

        /// <summary>
        /// Sets and gets the IsAllDayEvent property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public bool IsAllDayEvent
        {
            get
            {
                return _isAllDayEvent;
            }

            set
            {
                if (_isAllDayEvent == value)
                {
                    return;
                }

                RaisePropertyChanging(IsAllDayEventPropertyName);
                _isAllDayEvent = value;
                RaisePropertyChanged(IsAllDayEventPropertyName);

                if (!IsAllDayEvent && !StartTime.HasValue && !EndTime.HasValue)
                {
                    StartTime = GeneralUtilities.GetOclockTime(GeneralUtilities.GetCurrentDateTime());
                    EndTime = StartTime.Value.AddHours(1);
                    IsStartTimeSpecified = true;
                    IsEndTimeSpecified = true;
                }
            }
        }

        private RelayCommand _okCommand;

        /// <summary>
        /// Gets the OkCommand.
        /// </summary>
        public RelayCommand OkCommand
        {
            get
            {
                return _okCommand ?? (_okCommand = new RelayCommand(
                    ExecuteOkCommand,
                    CanExecuteOkCommand));
            }
        }

        private void ExecuteOkCommand()
        {
            if (IsAllDayEvent)
            {
                StartTime = null;
                EndTime = null;
            }
            else
            {
                if (!IsStartTimeSpecified)
                {
                    StartTime = null;
                }
                else
                {
                    StartTime = DueDate.Value.Date.Add(StartTime.Value.TimeOfDay);
                }

                if (!IsEndTimeSpecified)
                {
                    EndTime = null;
                }
                else
                {
                    EndTime = DueDate.Value.Date.Add(EndTime.Value.TimeOfDay);
                }
            }

            // Send message to DueTimeDisplayViewModel to update the DueDate & Time.
            Messenger.Default.Send<DueTimePickerViewModel>(this, SelectDueDateTimeCompleteMessageToken);
        }

        private bool CanExecuteOkCommand()
        {
            return ValidateDueDateTime();
        }

        private RelayCommand _cancelCommand;

        /// <summary>
        /// Gets the CancelCommand.
        /// </summary>
        public RelayCommand CancelCommand
        {
            get
            {
                return _cancelCommand
                    ?? (_cancelCommand = new RelayCommand(
                                          () =>
                                          {
                                              // Send message to DueTimePickerWindow to close the window.
                                              Messenger.Default.Send<int>(0, RequestCloseDueTimePickerWindowMessageToken);
                                          }));
            }
        }

        private bool ValidateDueDateTime()
        {
            bool isValid = true;

            if (!ValidateDueDateEarlierThenCurrentDate())
            {
                // Need to specify the DueDate later then current date.
                isValid = false;
            }
            else if (!ValidateSetDueTimeWithoutDueDate())
            {
                // Need to specify the DueDate before setup the time.
                isValid = false;
            }
            else if (!ValidateSetNotAllDayEventWithoutDueTime())
            {
                // If does not specify StartTime or EndTime then it must be a All Day Event.
                isValid = false;
            }
            // If user checks StartTime but does not select the value.
            else if (!ValidateMissedStartTime())
            {
                isValid = false;
            }
            else if (!ValidateMissedEndTime())
            {
                // If user checks EndTime but does not select the value.
                isValid = false;
            }
            else if (!ValidateSetStartTimeLaterThenEndTime())
            {
                // Start Time cannot be later than End Time.
                isValid = false;
            }

            return isValid;
        }

        /// <summary>
        /// Need to specify the DueDate later then current date.
        /// </summary>
        /// <returns>True means valid. False means invalid.</returns>
        private bool ValidateDueDateEarlierThenCurrentDate()
        {
            if (DueDate.HasValue && DueDate.Value.Date < GeneralUtilities.GetCurrentDateTime().Date)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        /// <summary>
        /// Need to specify the DueDate before setup the time.
        /// </summary>
        /// <returns>True means valid. False means invalid.</returns>
        private bool ValidateSetDueTimeWithoutDueDate()
        {
            if (!DueDate.HasValue && !IsAllDayEvent && (StartTime.HasValue || EndTime.HasValue))
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        /// <summary>
        /// If does not specify StartTime or EndTime then it must be a All Day Event.
        /// </summary>
        /// <returns>True means valid. False means invalid.</returns>
        private bool ValidateSetNotAllDayEventWithoutDueTime()
        {
            if (DueDate.HasValue && !IsAllDayEvent && !IsStartTimeSpecified && !IsEndTimeSpecified)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        /// <summary>
        /// If user checks StartTime but does not select the value.
        /// </summary>
        /// <returns>True means valid. False means invalid.</returns>
        private bool ValidateMissedStartTime()
        {
            if (IsStartTimeSpecified && !StartTime.HasValue)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        /// <summary>
        /// If user checks EndTime but does not select the value.
        /// </summary>
        /// <returns>True means valid. False means invalid.</returns>
        private bool ValidateMissedEndTime()
        {
            if (IsEndTimeSpecified && !EndTime.HasValue)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        /// <summary>
        /// Start Time cannot be later than End Time.
        /// </summary>
        /// <returns>True means valid. False means invalid.</returns>
        private bool ValidateSetStartTimeLaterThenEndTime()
        {
            if (DueDate.HasValue && !IsAllDayEvent && 
                IsStartTimeSpecified && IsEndTimeSpecified && 
                StartTime.HasValue && EndTime.HasValue && 
                StartTime.Value.TimeOfDay > EndTime.Value.TimeOfDay)
            {
                return false;
            }
            else
            {
                return true;
            }
        }
    }
}